﻿using HardcoreGame.Common.Helpers;
using HardcoreGame.Common.Players;
using HardcoreGame.Content.Buffs;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Terraria;
using Terraria.Audio;
using Terraria.ID;
using Terraria.ModLoader;

namespace HardcoreGame.Common.Cores.Logic
{
    internal class StaminaLogic
    {
        private float _stamina;
        public IDictionary<string, float> CurrentDrainTypes = new Dictionary<string, float>();
        internal IList<StaminaChangeHook> StaminaChangeHooks = [];
        internal IList<FatigueChangeHook> FatigueChangeHooks = [];
        public float Stamina
        {
            get
            {
                return _stamina;
            }
            private set
            {
                float max = MaxStamina2 - Fatigue;
                value = value < 0f ? 0f : value > max ? max : value;
                _stamina = value;
            }
        }
        public float CurrentDrain;
        public float CurrentDrainMost;
        public int CurrentDrainCount;
        public int MaxStamina;
        public int MaxStamina2;
        public float Fatigue;
        public int FatigueRecoverTimer;
        public bool DrainingFX;
        public bool HasStaminaSet;
        public double TiredTimer;
        public int ItemUseDrainDuration;
        public bool IsExercising;
        public StaminaLogic(int maxStamina, bool hasStamina)
        {
            MaxStamina = maxStamina;
            MaxStamina2 = maxStamina;
            HasStaminaSet = hasStamina;
            Fatigue = 0f;
            FatigueRecoverTimer = 0;
            CurrentDrain = 0f;
            CurrentDrainMost = 0f;
            CurrentDrainCount = 0;
            DrainingFX = false;
            ItemUseDrainDuration = 0;
            IsExercising = false;
            if (!hasStamina)
            {
                MaxStamina = 100;
                MaxStamina2 = 400;
                HasStaminaSet = true;
            }
            Stamina = 1f;
            TiredTimer = 0.0;
        }
        internal StaminaLogic Clone()
        {
            return new StaminaLogic(MaxStamina, HasStaminaSet)
            {
                Fatigue = Fatigue,
                FatigueRecoverTimer = FatigueRecoverTimer,
                CurrentDrain = CurrentDrain,
                CurrentDrainMost = CurrentDrainMost,
                CurrentDrainCount = CurrentDrainCount,
                DrainingFX = DrainingFX,
                ItemUseDrainDuration = ItemUseDrainDuration,
                IsExercising = IsExercising,
                Stamina = Stamina,
                TiredTimer = TiredTimer
            };
        }
        public void UpdateMaxStamina2(Player player)
        {
            MaxStamina2 = MaxStamina;
            if (player.FindBuffIndex(ModContent.BuffType<Athletic>()) != -1)
            {
                MaxStamina2 += Athletic.MaxStaminaAdd;
            }
        }
        public void SetItemUseHoldDurationForStaminaDrain(int amt)
        {
            ItemUseDrainDuration = amt;
        }
        public void PassiveStaminaRegen(Player player)
        {
            if (Stamina > 0f)
            {
                TiredTimer = 0.0;
                AddStamina(player, 0.45f);
                return;
            }
            if (TiredTimer >= 160)
            {
                TiredTimer = 0.0;
                Stamina = 0.0001f;
                AddStamina(player, 18f);
                return;
            }
            TiredTimer += 1.0;
        }
        public void GatherPassiveStaminaDrains(Player player)
        {
            HardcoreGame mymod = HardcoreGame.Instance;
            if (player.grappling[0] >= 0)
            {
                DrainStaminaViaGrappleHold(player);
            }
            if (ItemUseDrainDuration > 0)
            {
                int itemUseDrainDuration = ItemUseDrainDuration;
                ItemUseDrainDuration = itemUseDrainDuration - 1;
                Item currItem = player.inventory[player.selectedItem];
                if (currItem == null || currItem.IsAir)
                {
                    return;
                }
                bool isPewpew = currItem.type == ItemID.SpaceGun || currItem.type == ItemID.LaserRifle;
                bool isSpaceman = player.armor[0].type == ItemID.MeteorHelmet && player.armor[1].type == ItemID.MeteorSuit && player.armor[2].type == ItemID.MeteorLeggings;
                DrainStaminaViaCustomItemUse(player);
                if (currItem.CountsAsClass(DamageClass.Magic) && (!isPewpew || !isSpaceman))
                {
                    DrainStaminaViaMagicItemUse(player);
                }
                else
                {
                    DrainStaminaViaItemUse(player);
                }
            }
        }
        public void GatherActivityStaminaDrains(Player player)
        {
            StaminaPlayer myplayer = player.GetModPlayer<StaminaPlayer>();
            if (!player.mount.Active && player.velocity.Y == 0f && player.dashDelay >= 0)
            {
                float runMin = PlayerHelper.MinimumRunSpeed(player);
                float acc = player.accRunSpeed + 0.1f;
                float velX = player.velocity.X;
                if (player.controlRight && velX > runMin && velX < acc || player.controlLeft && velX < -runMin && velX > -acc)
                {
                    DrainStaminaViaSprint(player);
                }
            }
            if (!myplayer.IsDashing)
            {
                if (player.dash != 0 && player.dashDelay == -1)
                {
                    DrainStaminaViaDash(player);
                    myplayer.IsDashing = true;
                }
            }
            else if (player.dashDelay != -1)
            {
                myplayer.IsDashing = false;
            }
            if (player.controlJump)
            {
                if (!myplayer.IsJumping && !PlayerHelper.IsFlying(player))
                {
                    if (player.swimTime > 0)
                    {
                        DrainStaminaViaSwimBegin(player);
                    }
                    else if (player.velocity.Y == 0f || player.sliding || player.GetJumpState<BlizzardInABottleJump>().Enabled || player.GetJumpState<CloudInABottleJump>().Enabled || player.GetJumpState<FartInAJarJump>().Enabled || player.GetJumpState<SandstormInABottleJump>().Enabled)
                    {
                        DrainStaminaViaJumpBegin(player);
                    }
                    myplayer.IsJumping = true;
                }
                if (player.jump > 0 || PlayerHelper.IsFlying(player))
                {
                    if (player.swimTime > 0)
                    {
                        DrainStaminaViaSwimHold(player);
                        return;
                    }
                    DrainStaminaViaJumpHold(player);
                    return;
                }
            }
            else if (myplayer.IsJumping)
            {
                myplayer.IsJumping = false;
            }
        }
        public void DrainStamina(Player player, StaminaDrainTypes type)
        {
            switch (type)
            {
                case StaminaDrainTypes.ItemUse:
                    DrainStaminaViaItemUse(player);
                    return;
                case StaminaDrainTypes.MagicItemUse:
                    DrainStaminaViaMagicItemUse(player);
                    return;
                case StaminaDrainTypes.GrappleBegin:
                    DrainStaminaViaGrappleBegin(player);
                    return;
                case StaminaDrainTypes.GrappleHold:
                    DrainStaminaViaGrappleHold(player);
                    return;
                case StaminaDrainTypes.Sprint:
                    DrainStaminaViaSprint(player);
                    return;
                case StaminaDrainTypes.Dash:
                    DrainStaminaViaDash(player);
                    return;
                case StaminaDrainTypes.SwimBegin:
                    DrainStaminaViaSwimBegin(player);
                    return;
                case StaminaDrainTypes.SwimHold:
                    DrainStaminaViaSwimHold(player);
                    return;
                case StaminaDrainTypes.JumpBegin:
                    DrainStaminaViaJumpBegin(player);
                    return;
                case StaminaDrainTypes.JumpHold:
                    DrainStaminaViaJumpHold(player);
                    return;
            }
        }
        public void DrainStaminaViaItemUse(Player player)
        {
            float amt = 0.501f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.ItemUse);
        }
        public void DrainStaminaViaMagicItemUse(Player player)
        {
            float amt = 0.2f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.MagicItemUse);
        }
        public void DrainStaminaViaCustomItemUse(Player player)
        {
            float amt = 0.15f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.CustomItemUse);
        }
        public void DrainStaminaViaGrappleBegin(Player player)
        {
            float amt = 12f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.GrappleBegin);
        }
        public void DrainStaminaViaGrappleHold(Player player)
        {
            float amt = 0.45f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.GrappleHold);
        }
        public void DrainStaminaViaSprint(Player player)
        {
            float amt = 0.5f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.Sprint);
        }
        public void DrainStaminaViaDash(Player player)
        {
            float amt = 28f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.Dash);
        }
        public void DrainStaminaViaSwimBegin(Player player)
        {
            float amt = 2f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.SwimBegin);
        }
        public void DrainStaminaViaSwimHold(Player player)
        {
            float amt = 0.5f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.SwimHold);
        }
        public void DrainStaminaViaJumpBegin(Player player)
        {
            float amt = 6.5f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.JumpBegin);
        }
        public void DrainStaminaViaJumpHold(Player player)
        {
            float amt = 0.65f;
            DrainStaminaAmount(player, amt, StaminaDrainTypes.JumpHold);
        }
        public void DrainStaminaCustomAmount(Player player, float amount, string customType)
        {
            DrainStaminaAmount(player, amount, StaminaDrainTypes.Custom);
            if (CurrentDrainTypes.ContainsKey(customType))
            {
                IDictionary<string, float> currentDrainTypes = CurrentDrainTypes;
                currentDrainTypes[customType] += amount;
                return;
            }
            CurrentDrainTypes[customType] = amount;
        }
        public void DrainStaminaAmount(Player player, float amount, StaminaDrainTypes type)
        {
            int currentDrainCount = CurrentDrainCount;
            CurrentDrainCount = currentDrainCount + 1;
            foreach (StaminaChangeHook staminaChangeHook in StaminaChangeHooks)
            {
                amount = staminaChangeHook(player, type, amount);
            }
            if (amount > CurrentDrainMost)
            {
                CurrentDrain += CurrentDrainMost;
                CurrentDrainMost = amount;
            }
            else
            {
                CurrentDrain += amount;
            }
            string baseType = type.ToString();
            if (CurrentDrainTypes.ContainsKey(baseType))
            {
                IDictionary<string, float> currentDrainTypes = CurrentDrainTypes;
                string text = baseType;
                currentDrainTypes[text] += amount;
                return;
            }
            CurrentDrainTypes[baseType] = amount;
        }
        public void CommitStaminaDrains(Player player)
        {
            if (CurrentDrainCount == 0)
            {
                DrainingFX = false;
                return;
            }
            float drain = CurrentDrainMost + CurrentDrain / (CurrentDrainCount * 2f);
            if (Stamina == 0f)
            {
                TiredTimer = (int)drain > TiredTimer ? 0.0 : TiredTimer - (double)drain;
            }
            else
            {
                Stamina -= drain * 1f;
                if (Stamina <= 0f)
                {
                    AddFatigue(player, 12f);
                }
            }
            DrainingFX = true;
            CurrentDrain = 0f;
            CurrentDrainMost = 0f;
            CurrentDrainCount = 0;
        }
        public int GetStaminaLossAmountNeededForExercise()
        {
            return (int)(MaxStamina2 * 0.24f);
        }
        public bool ApplyExercise(Player player)
        {
            if (MaxStamina >= 400)
            {
                return false;
            }
            StaminaPlayer modPlayer = player.GetModPlayer<StaminaPlayer>();
            AddMaxStamina(player, 3);
            if (MaxStamina > 400)
            {
                AddMaxStamina(player, 400 - MaxStamina);
            }
            SoundEngine.PlaySound(SoundID.Item47.WithVolumeScale(0.5f));
            return true;
        }
        public void PassiveFatigueRecover(Player player)
        {
            if (Fatigue > 0f)
            {
                if (MaxStamina2 - Stamina <= Fatigue)
                {
                    int fatigueRecoverTimer = FatigueRecoverTimer;
                    FatigueRecoverTimer = fatigueRecoverTimer + 1;
                    int duration = 60;
                    if (FatigueRecoverTimer >= duration)
                    {
                        FatigueRecoverTimer = 0;
                        AddFatigue(player, -1f);
                    }
                }
                if (Fatigue >= GetStaminaLossAmountNeededForExercise())
                {
                    IsExercising = true;
                    return;
                }
            }
            else if (IsExercising)
            {
                IsExercising = false;
                ApplyExercise(player);
            }
        }
        public void AddStamina(Player player, float amount)
        {
            amount *= 1f;
            if (Stamina == 0f)
            {
                TiredTimer += (double)(amount / 2f);
                return;
            }
            foreach (StaminaChangeHook staminaChangeHook in StaminaChangeHooks)
            {
                amount = staminaChangeHook(player, StaminaDrainTypes.Recover, amount);
            }
            Stamina += amount;
        }
        public void AddFatigue(Player player, float amount)
        {
            foreach (FatigueChangeHook fatigueChangeHook in FatigueChangeHooks)
            {
                amount = fatigueChangeHook(player, amount);
            }
            Fatigue += amount;
            Fatigue = MathHelper.Clamp(Fatigue, 0f, MaxStamina2);
        }
        public void AddMaxStamina(Player player, int amount)
        {
            MaxStamina = Math.Max(MaxStamina + amount, 0);
            UpdateMaxStamina2(player);
            if (amount < 0 && Stamina > MaxStamina2)
            {
                Stamina = MaxStamina2;
            }
        }
    }
    public delegate float StaminaChangeHook(Player player, StaminaDrainTypes drainType, float amount);
    public delegate float FatigueChangeHook(Player player, float amount);
    public enum StaminaDrainTypes
    {
        ItemUse,
        MagicItemUse,
        GrappleBegin,
        GrappleHold,
        Sprint,
        Dash,
        SwimBegin,
        SwimHold,
        JumpBegin,
        JumpHold,
        GravitationPotion,
        Custom,
        CustomItemUse,
        Recover
    }
}
